package edu.kpi.pzks2.simulator.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import edu.kpi.pzks2.graph.Edge;
import edu.kpi.pzks2.graph.Node;
import edu.kpi.pzks2.simulator.core.QueueBuilder;
import edu.kpi.pzks2.simulator.core.Simulator;
import edu.kpi.pzks2.simulator.core.strategy.Strategy;
import edu.kpi.pzks2.simulator.core.strategy.StrategyFactory;
import edu.kpi.pzks2.simulator.links.Message;
import edu.kpi.pzks2.simulator.processing.Processor;
import edu.kpi.pzks2.simulator.processing.Task;
import edu.uci.ics.jung.algorithms.shortestpath.DijkstraShortestPath;
import edu.uci.ics.jung.graph.Graph;

public class NeighbourStrategy extends BaseStrategyImpl {

    private List<Node> procQueue;
    
    private NeighbourStrategy(final Graph<Node, Edge> taskGraph, final Graph<Node, Edge> procGraph, 
            final QueueBuilder queueBuilder, final Simulator simulator) {
        super(taskGraph, procGraph, queueBuilder, simulator);
        procQueue = generateProcQueue();
    }

    protected List<Processor>getReadyProcessors() {
        final List<Processor> procs = new ArrayList<Processor>();
        for (Node node : procQueue) {
            final Processor proc = simulator.getCorrespondProcessor(node);
            if (!proc.isBusy()) {
                procs.add(proc);
            }
        }
        return procs;
    }
    
    public void makeStep() {
        final List<Node> nodes = getReadyNodes();
        final List<Processor> processors = getReadyProcessors();
        while (!nodes.isEmpty() && !processors.isEmpty()) {
            // by default use first free proc
            Processor procTo = processors.get(0);
            final Node node = nodes.get(0);
            
            // collect all predecessors
            final Set<Integer> predecessors = new HashSet<Integer>();
            for (Node pred : taskGraph.getPredecessors(node)) {
                predecessors.add(pred.getId());
            }
            
            // on the proc with predecessor with largest edge weight
            int minPathWeight = Integer.MAX_VALUE;
            for (Processor proc : processors) {
                    int currentPathWeight = 0;
                for (Node pred : taskGraph.getPredecessors(node)) {
                    final DijkstraShortestPath<Node, Edge> shortPath = new DijkstraShortestPath<Node, Edge>(procGraph);
                    final List<Edge> path = shortPath.getPath(processedTasks.get(pred).getNode(), proc.getNode());
                    
                    final Edge edge = getEdgeBetweenAandB(pred, node);
                    if (edge == null) {
                        System.out.println("AAAA");
                    }
                    
                    currentPathWeight += edge.getWeight() * path.size();
                }
                
                if (minPathWeight > currentPathWeight) {
                        minPathWeight = currentPathWeight;
                        procTo = proc;
                }
            }
            
            procTo.setTask(new Task(node, predecessors));
            processingTasks.put(node, procTo);
            
            for (Node pred : taskGraph.getPredecessors(node)) {
                // if predecessor wasn't processed on the same processor
                if (processedTasks.get(pred) != procTo) {
                    final Edge edge = getEdgeBetweenAandB(pred, node);
                    if (edge == null) {
                        System.out.println("AAAA");
                    }
                    
                    final Processor procFrom = processedTasks.get(pred);
                    final Message msg = new Message(edge.getWeight(), pred.getId(), node.getId(), procTo, procFrom);
                    procFrom.receiveMessage(msg);
                }
            }
            
            processors.remove(procTo);
            nodes.remove(node);
        }
    }
    
    private List<Node> generateProcQueue() {
        final List<Node> nodes = new ArrayList<Node>();
        nodes.addAll(procGraph.getVertices());
        
        Collections.sort(nodes, new Comparator<Node>() {

            public int compare(Node arg0, Node arg1) {
                return procGraph.getNeighborCount(arg1) - procGraph.getNeighborCount(arg0);
            }
            
        });
        
        return nodes;
    }

    public static class NeighbourStrategyFactory implements StrategyFactory {
        public Strategy createStrategy(
                final Graph<Node, Edge> taskGraph,
                final Graph<Node, Edge> procGraph, 
                final QueueBuilder queueBuilder,
                final Simulator simulator) {
            return new NeighbourStrategy(taskGraph, procGraph, queueBuilder, simulator);
        }
    }
}
